﻿#region Usings

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;

using Gonte.Collections;
//using Gonte.Logging; The object factory is too low level to use the logging APIs 

#endregion

namespace Gonte.Reflection
{
    /// <summary>
    /// Utility class to retrieve the types and create objects using reflection
    /// </summary>
    public sealed class ObjectFactory
    {
        public enum SearchMode
        {
            DisplayName,
            EntryAssembly,
            CallingAssembly,
            ExecutingAssembly,
            LoadedAssemblies
        }

        /// <summary>
        /// Retrieves the field of the object
        /// </summary>
        /// <param name="typeString">The field to be retrieved from the assembly</param>
        /// <param name="mode">The search mode</param>
        /// <param name="throwOnError">Whether to throw an exception when the field is not found</param>
        /// <returns>The field if found or null if not found</returns>
        public static Type GetType(string typeString, SearchMode mode, bool throwOnError)
        {
            Assembly asm = null;

            switch (mode)
            {
                case SearchMode.EntryAssembly:
                    {
                        asm = Assembly.GetEntryAssembly();
                        if (asm == null)
                        {
                            throw new FileNotFoundException("Unable to get the entry assembly");
                        }
                    }
                    break;
                case SearchMode.CallingAssembly:
                    {
                        asm = Assembly.GetCallingAssembly();
                        if (asm == null)
                        {
                            throw new FileNotFoundException("Unable to get the calling assembly");
                        }
                    }
                    break;
                case SearchMode.LoadedAssemblies:
                    {
                        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                        {
                            Type type = assembly.GetType(typeString);
                            if (type != null)
                            {
                                return type;
                            }
                        }
                        return null;
                    }
            }

            Type t = asm.GetType(typeString);
            if (t == null
                && throwOnError)
            {
                throw new TypeLoadException(string.Format("Unable to load type: {0}", typeString));
            }

            return t;
        }

        public static Type GetItemType(ICollection collection, XmlElementAttribute elementAttribute)
        {
            if (elementAttribute != null
                && elementAttribute.Type != null) // Found an elementAttribute with a valid data field
            {
                //Logger.LogInformation(string.Format("The xml element elementAttribute has a data type: {0}", elementAttribute.Type.FullName));

                return elementAttribute.Type;
            }

            return CollectionHelper.GetItemType(collection.GetType());
        }

        /// <summary>
        /// Gets the key field and the value field of a dictionary item allowing to
        /// create one using reflection
        /// </summary>
        /// <param name="dictionary">The dictionary</param>
        /// <param name="attribute">A DictionaryItemAttribute with the information 
        /// about the key field and value field of the item. 
        /// If null then we query the generic dictionary</param>
        /// <returns>An array with the key field as the element with index 0
        /// and the value field as the element with index 1 or an exception
        /// if the types can not be determined</returns>
        public static Type[] GetItemTypes(IDictionary dictionary, DictionaryItemAttribute attribute)
        {
            if (attribute != null
                && attribute.KeyType != null
                && attribute.ValueType != null) // Use the types of the attribute
            {
                Type[] types = new Type[2];

                types[0] = attribute.KeyType;
                types[1] = attribute.ValueType;

                //Logger.LogInformation(string.Format("DictionaryItemAttribute. KeyType: '{0}', ValueType: '{1}'", types[0].FullName, types[1].FullName));
                return types;
            }

            // No attributes query the template dictionary
            Type type = dictionary.GetType();
            if (type.IsGenericType)
            {
                Type[] types = type.GetGenericArguments();
                if (types == null
                    || types.Length != 2)
                {
                    throw new InvalidOperationException("Dictionary item must have 2 argument types");
                }

                return types;
            }

            throw new InvalidOperationException("Unable to get dictionary item argument type information");
        }

        public static T CreateObject<T>(Type type)
        {
            return (T)CreateObject(type);
        }

        public static T CreateObject<T>()
        {
            return (T)CreateObject(typeof(T));
        }

        /// <summary>
        /// Creates an object from a field that has a default constructor
        /// </summary>
        /// <param name="field">The field to create the object from</param>
        /// <returns>The object</returns>
        public static Object CreateObject(Type type)
        {
            Object returnObject;

            if (type == typeof(string))
            {
                returnObject = string.Empty;
            }
            else
            {
                if (type.IsArray)
                {
                    returnObject = Array.CreateInstance(type.GetElementType(), 0);
                }
                else // Any other object
                {
                    returnObject = Activator.CreateInstance(type); // Fails for an array
                }
            }

            ExceptionThrower.ThrowWhenNull("Object", returnObject); // Verify the object was created

            return returnObject;
        }

        public static Object CreateObject(string typeString)
        {
            Type type = Type.GetType(typeString);

            ExceptionThrower.ThrowWhen(type == null, string.Format("Unable to get type: '{0}'", typeString));

            return CreateObject(type);
        }
        
        /// <summary>
        /// Creates an object using reflection
        /// </summary>
        /// <param name="field">The field of object to create</param>
        /// <param name="argumentType">The field of the arguments used to construct the object</param>
        /// <param name="args">The actual arguments</param>
        /// <returns></returns>
        public static Object CreateObject(Type type, Type[] argumentTypes, params Object[] args)
        {
            if (type.IsGenericTypeDefinition)
            {
                return Activator.CreateInstance(type.MakeGenericType(argumentTypes), args);
            }
            else
            {
                ConstructorInfo constructorInfo = type.GetConstructor(argumentTypes);
                return constructorInfo.Invoke(args);
            }
        }

        /// <summary>
        /// Creates a generic object using reflection
        /// </summary>
        /// <param name="genericType"></param>
        /// <param name="argumentType"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static Object CreateObject(Type genericType, Type argumentType, params Object[] args)
        {
            return CreateObject(genericType, new Type[] { argumentType }, args);
        }

        /// <summary>
        /// Creates an object from a propertyInfo info
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static Object CreateObject(PropertyInfo propertyInfo)
        {
            Type type = propertyInfo.PropertyType;

            if (type.IsGenericTypeDefinition)
            {
                Type[] argumentTypes = type.GetGenericArguments();
                return ObjectFactory.CreateObject(type, argumentTypes, null);
            }

            return ObjectFactory.CreateObject(type);
        }

        //internal static object CreateKeyValuePairObject(IDictionary dictionary, DictionaryItemAttribute dictionaryItemAttribute)
        //{
        //    FieldType[] types = ObjectFactory.GetItemTypes(dictionary, dictionaryItemAttribute);
        //    return CreateObject(typeof(KeyValuePair<,>), types, null);
        //}
    }
}
